/**
 *  This file is part of MunchLevel.
 *
 *  MunchLevel is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  MunchLevel is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with MunchLevel.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.munchlevel;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.os.AsyncTask;
import android.os.AsyncTask.Status;
import android.os.Bundle;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.animation.AnimationUtils;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.gandulf.guilib.util.Debug;
import com.munchlevel.data.PlayerInfo;
import com.munchlevel.util.BluetoothService;
import com.munchlevel.util.BluetoothService.BluetoothServiceListener;
import com.munchlevel.util.SoundManager;
import com.munchlevel.util.ThemeManager;
import com.munchlevel.util.ThemeManager.ThemeItem;
import com.munchlevel.view.BasePlayerLayout;
import com.munchlevel.view.DiceView;
import com.munchlevel.view.PlayerFightLayout;
import com.munchlevel.view.adapter.PlayerAdapter;
import com.munchlevel.view.listener.OnPlayerChangedListener;
import com.munchlevel.view.listener.ShakeListener;

public class FightActivity extends BaseGameActivity implements OnClickListener, OnLongClickListener,
		OnPlayerChangedListener, BluetoothServiceListener {

	private LinearLayout rootLayout;
	private PlayerFightLayout playerLayout;
	private PlayerFightLayout helperLayout;
	private LinearLayout playerVsLayout;

	private List<PlayerInfo> remotePlayerInfos = new LinkedList<PlayerInfo>();
	private PlayerFightLayout monsterLayout;

	private DiceView ibDice6;
	private ImageButton ibHelp;
	private ShakeListener mShaker;

	private TextView playerTotal, monsterTotal;

	private SoundManager soundManager;

	private DeviceChecker mDeciveChecker;

	private PlayerInfo playerInfo;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.fight);
		this.setVolumeControlStream(AudioManager.STREAM_MUSIC);

		playerInfo = getGameInfo().getSelectedPlayerInfo();

		rootLayout = (LinearLayout) findViewById(R.id.root);
		rootLayout.setWeightSum(1.5F);

		// -- seperator
		playerVsLayout = (LinearLayout) getLayoutInflater().inflate(R.layout.player_fight_vs, rootLayout, false);
		playerVsLayout.setOnLongClickListener(this);
		LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) playerVsLayout.getLayoutParams();
		layoutParams.weight = 0.5F;
		layoutParams.width = 0;
		rootLayout.addView(playerVsLayout);

		playerTotal = (TextView) playerVsLayout.findViewById(R.id.tv_fight_players);
		monsterTotal = (TextView) playerVsLayout.findViewById(R.id.tv_fight_monsters);

		MunchLevelApplication.applyFont(playerTotal, monsterTotal,
				(TextView) playerVsLayout.findViewById(R.id.tv_fight_vs));

		// -- player layouts
		playerLayout = addPlayerFightLayout(0, playerInfo);

		// -- monster
		monsterLayout = (PlayerFightLayout) getLayoutInflater().inflate(R.layout.player_fight, rootLayout, false);
		monsterLayout.setOnPlayerChangedListener(this);
		monsterLayout.setMaximumLevel(99);
		monsterLayout.setPlayerName("Monster");

		layoutParams = (LinearLayout.LayoutParams) monsterLayout.getLayoutParams();
		layoutParams.weight = 1;
		layoutParams.width = 0;
		rootLayout.addView(monsterLayout);

		// --
		ibDice6 = (DiceView) findViewById(R.id.iv_dice);
		ibDice6.setOnClickListener(this);

		ibHelp = (ImageButton) findViewById(R.id.ib_help);
		ibHelp.setOnClickListener(this);

		if (isHelpAvailable()) {
			registerForContextMenu(playerLayout);
			ibHelp.setVisibility(View.VISIBLE);
		} else {
			ibHelp.setVisibility(View.GONE);
		}

		updateTotals();

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.munchlevel.util.BluetoothService.BluetoothServiceListener#
	 * onCommandReceived(java.lang.String)
	 */
	@Override
	public boolean onCommandReceived(String cmd, String params) {
		if (cmd.equals(CMD_REQUEST_CONNECT_INFO)) {
			getBluetoothService().sendCommand(CMD_CONNECT_INFO, playerLayout.getPlayerInfo().toString());
			return true;
		} else if (cmd.equals(CMD_REQUEST_ADD_INFO)) {
			getBluetoothService().sendCommand(CMD_ADD_INFO, playerLayout.getPlayerInfo().toString());
			return true;
		} else if (cmd.equals(CMD_ADD_INFO)) {
			if (mDeciveChecker != null) {
				mDeciveChecker.addConfirmedDevice(getBluetoothService().getRemoteAddress());
			}
			PlayerInfo playerinfo = PlayerInfo.parse(params);
			remotePlayerInfos.add(playerinfo);
			return true;
		} else if (cmd.equals(CMD_CONNECT_INFO)) {
			if (mDeciveChecker != null) {
				mDeciveChecker.addConfirmedDevice(getBluetoothService().getRemoteAddress());
			}
			PlayerInfo playerinfo = PlayerInfo.parse(params);
			setHelperLayout(playerinfo);
			return true;
		}

		return false;
	}

	@Override
	public void onConnected(String deviceName, String address) {
		if (mDeciveChecker != null) {
			mDeciveChecker.addConnectedDevice(address);
		}
	}

	@Override
	public void onConnectFailed(String name, String address) {
		if (mDeciveChecker != null) {
			mDeciveChecker.addFailedDevice(address);
		}
	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
			setResult(RESULT_CANCELED);
			finish();
			return true;
		}

		return super.onKeyDown(keyCode, event);
	}

	private PlayerFightLayout addPlayerFightLayout(int index, PlayerInfo info) {

		rootLayout.setWeightSum(rootLayout.getWeightSum() + 1.0f);

		final PlayerFightLayout playerLayout = (PlayerFightLayout) getLayoutInflater().inflate(R.layout.player_fight,
				rootLayout, false);

		LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) playerLayout.getLayoutParams();
		layoutParams.weight = 1;
		layoutParams.width = 0;
		rootLayout.addView(playerLayout, index);

		playerLayout.setPlayerInfo(info);
		playerLayout.setOnPlayerChangedListener(this);

		rootLayout.requestLayout();

		return playerLayout;
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.menu.fight_menu, menu);

		return super.onCreateOptionsMenu(menu);
	}

	@Override
	public boolean onPrepareOptionsMenu(Menu menu) {
		MenuItem menuItemHelp = menu.findItem(R.id.fight_help);
		MenuItem menuItemNot = menu.findItem(R.id.fight_help_not);

		menuItemHelp.setVisible(getGameInfo().getPlayers() > 1 && helperLayout == null);
		menuItemNot.setVisible(helperLayout != null);

		return super.onPrepareOptionsMenu(menu);
	}

	private void checkBluetoothDevices() {
		if (mDeciveChecker == null || mDeciveChecker.getStatus() == Status.FINISHED) {
			mDeciveChecker = new DeviceChecker();
			remotePlayerInfos.clear();
			mDeciveChecker.execute(MunchLevelApplication.getInstance().confirmedDevices.toArray(new String[0]));
		}
	}

	private void callForHelp() {
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		builder.setTitle(R.string.choose_player);

		List<PlayerInfo> infos = new ArrayList<PlayerInfo>(getGameInfo().getPlayers() - 1);
		for (PlayerInfo info : getGameInfo().getPlayer()) {
			if (info == playerInfo)
				continue;

			infos.add(info);
		}

		if (remotePlayerInfos != null) {
			infos.addAll(remotePlayerInfos);
		}

		final PlayerAdapter adapter = new PlayerAdapter(this, 0, infos);

		builder.setAdapter(adapter, new DialogInterface.OnClickListener() {

			@Override
			public void onClick(DialogInterface dialog, int which) {
				if (adapter.isSelectDevice(which)) {
					checkBluetoothDevices();
				} else {
					PlayerInfo info = adapter.getItem(which);
					setHelperLayout(info);
				}
			}

		});
		builder.show();
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {
		case R.id.fight_help:
			callForHelp();
			return true;
		case R.id.fight_help_not:
			setHelperLayout(null);
			return true;
		}

		return super.onOptionsItemSelected(item);
	}

	private boolean isHelpAvailable() {
		return (getGameInfo().getPlayers() > 1 || !MunchLevelApplication.getInstance().confirmedDevices.isEmpty())
				&& helperLayout == null;
	}

	protected void setHelperLayout(PlayerInfo info) {

		findViewById(R.id.tt_fight).setVisibility(View.GONE);

		if (helperLayout != null) {
			if (isBluetoothAvailable())
				getBluetoothService().restart();
			rootLayout.removeView(helperLayout);
			rootLayout.setWeightSum(rootLayout.getWeightSum() - 1.0f);
			helperLayout = null;
		}

		if (info != null) {
			helperLayout = addPlayerFightLayout(1, info);
			registerForContextMenu(helperLayout);
			onSharedPreferenceChanged(PreferenceManager.getDefaultSharedPreferences(this), null);
		}

		if (isHelpAvailable()) {
			ibHelp.setVisibility(View.VISIBLE);
		} else {
			ibHelp.setVisibility(View.GONE);
		}

		updateTotals();
	}

	@Override
	public boolean onContextItemSelected(MenuItem item) {
		if (item.getItemId() == R.id.fight_help) {
			callForHelp();
			return true;
		} else if (item.getItemId() == R.id.fight_help_not) {
			setHelperLayout(null);
			return true;
		}
		return super.onContextItemSelected(item);
	}

	@Override
	public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
		if (v == helperLayout) {
			MenuItem item = menu.add(0, R.id.fight_help_not, 0, R.string.menu_fight_help_not);
			item.setIcon(android.R.drawable.ic_menu_myplaces);
		} else if (v == playerLayout) {
			MenuItem item;
			if (isHelpAvailable()) {
				item = menu.add(0, R.id.fight_help, 0, R.string.menu_fight_help);
				item.setIcon(android.R.drawable.ic_menu_myplaces);
			} else if (helperLayout != null) {
				item = menu.add(0, R.id.fight_help_not, 0, R.string.menu_fight_help_not);
				item.setIcon(android.R.drawable.ic_menu_myplaces);
			}

		}
		super.onCreateContextMenu(menu, v, menuInfo);
	}

	@Override
	public void onClick(View v) {
		if (v == ibDice6) {
			ibDice6.rollDice6();
		} else if (v.getId() == R.id.fight_banner_layout) {
			setResult(RESULT_OK);
			finish();
		} else if (v == ibHelp) {
			callForHelp();
		}

		switch (v.getId()) {

		case R.id.fight_level1:
			playerInfo.addLevel(1);
			setResult(RESULT_OK);
			finish();
			break;
		case R.id.fight_level2:
			playerInfo.addLevel(2);
			setResult(RESULT_OK);
			finish();
			break;
		}
	}

	@Override
	public boolean onLongClick(View v) {
		if (v == playerVsLayout) {

			soundManager = new SoundManager(this);
			if (playerLayout.getPlayerInfo().hasClazz(R.string.clazz_warrior)
					|| (helperLayout != null && helperLayout.getPlayerInfo().hasClazz(R.string.clazz_warrior))) {
				if (monsterLayout.getTotal() > getPlayerTotal()) {
					lose();
				} else {
					win();
				}
			} else {
				if (monsterLayout.getTotal() >= getPlayerTotal()) {
					lose();
				} else {
					win();
				}
			}

			return true;
		}
		return false;
	}

	private void win() {
		ImageView banner = (ImageView) findViewById(R.id.fight_banner);
		banner.setImageResource(R.drawable.win);

		ImageButton level1 = (ImageButton) findViewById(R.id.fight_level1);
		level1.setVisibility(View.VISIBLE);
		level1.setOnClickListener(this);

		ImageButton level2 = (ImageButton) findViewById(R.id.fight_level2);
		level2.setVisibility(View.VISIBLE);
		level2.setOnClickListener(this);

		showBanner();
	}

	private void showBanner() {
		ImageView banner = (ImageView) findViewById(R.id.fight_banner);

		banner.setVisibility(View.VISIBLE);
		banner.startAnimation(AnimationUtils.loadAnimation(this, R.anim.bounce));

		LinearLayout bannerBg = (LinearLayout) findViewById(R.id.fight_banner_layout);
		bannerBg.setVisibility(View.VISIBLE);
		bannerBg.startAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_in));
		bannerBg.setOnClickListener(this);
	}

	private void lose() {
		ImageView banner = (ImageView) findViewById(R.id.fight_banner);
		banner.setImageResource(R.drawable.lose);
		soundManager.play(R.raw.lose);

		ImageButton level1 = (ImageButton) findViewById(R.id.fight_level1);
		level1.setVisibility(View.GONE);
		ImageButton level2 = (ImageButton) findViewById(R.id.fight_level2);
		level2.setVisibility(View.GONE);

		showBanner();
	}

	public int getPlayerTotal() {
		int total = playerLayout.getTotal();
		if (helperLayout != null) {
			total += helperLayout.getTotal();
		}
		return total;
	}

	private void updateTotals() {
		monsterTotal.setText(Integer.toString(monsterLayout.getTotal()));
		playerTotal.setText(Integer.toString(getPlayerTotal()));

	}

	private void updateTotal(PlayerFightLayout layout) {

		if (layout == monsterLayout) {
			monsterTotal.setText(Integer.toString(layout.getTotal()));
		} else {

			int total = 0;

			if (playerLayout != null)
				total += playerLayout.getTotal();

			if (helperLayout != null)
				total += helperLayout.getTotal();

			playerTotal.setText(Integer.toString(total));
		}

	}

	@Override
	public void onGearChanged(BasePlayerLayout layout, int oldVal, int newVal) {

		updateBonusGainLoss(layout, oldVal, newVal);

		if (layout instanceof PlayerFightLayout)
			updateTotal((PlayerFightLayout) layout);
	}

	private void updateBonusGainLoss(BasePlayerLayout layout, int oldVal, int newVal) {

		int change = newVal - oldVal;

		if (layout == monsterLayout) {

			// for monsters the bonus gain is the opposite,
			if (change < 0) {
				playerInfo.addBonusGain(Math.abs(change));

				if (helperLayout != null) {
					helperLayout.getPlayerInfo().addBonusGain(Math.abs(change));
				}
			} else {
				playerInfo.addBonusLoss(change);
				if (helperLayout != null) {
					helperLayout.getPlayerInfo().addBonusLoss(change);
				}
			}

		} else {

			if (change < 0) {
				layout.getPlayerInfo().addBonusLoss(Math.abs(change));
			} else {
				layout.getPlayerInfo().addBonusGain(change);
			}

		}
	}

	@Override
	public void onLevelChanged(BasePlayerLayout layout, int oldVal, int newVal) {

		updateBonusGainLoss(layout, oldVal, newVal);

		if (layout instanceof PlayerFightLayout)
			updateTotal((PlayerFightLayout) layout);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.munchlevel.view.listener.OnPlayerChangedListener#onNameChanged(com
	 * .munchlevel.view.BasePlayerLayout, java.lang.String, java.lang.String)
	 */
	@Override
	public void onNameChanged(PlayerInfo info, String oldVal, String newVal) {
		// TODO
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.munchlevel.view.listener.OnPlayerChangedListener#onGenderChanged(
	 * com.munchlevel.view.BasePlayerLayout, boolean, boolean)
	 */
	@Override
	public void onGenderChanged(PlayerInfo info, boolean oldVal, boolean newVal) {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.munchlevel.BaseActivity#onSharedPreferenceChanged(android.content
	 * .SharedPreferences, java.lang.String)
	 */
	@Override
	public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {

		// gear counter
		if (key == null || MyPreferenceActivity.KEY_GEAR_COUNTER.equals(key)) {

			boolean gearVisible = sharedPreferences.getBoolean(MyPreferenceActivity.KEY_GEAR_COUNTER, true);

			playerLayout.setGearCounterVisibility(gearVisible);
			// playerLayout.setProfilePictureVisibility(cameraVisible);

			if (helperLayout != null) {
				helperLayout.setGearCounterVisibility(gearVisible);
				// helperLayout.setProfilePictureVisibility(cameraVisible);
			}
		}

		// -- level
		if (key == null || MyPreferenceActivity.KEY_MAX_LEVEL.equals(key)) {
			int max;
			try {
				max = Integer.valueOf(sharedPreferences.getString(MyPreferenceActivity.KEY_MAX_LEVEL, "10"));
			} catch (NumberFormatException e) {
				max = 10;
			}

			playerLayout.setMaximumLevel(max);
			if (helperLayout != null) {
				helperLayout.setMaximumLevel(max);
			}
		}

		if (key == null || MyPreferenceActivity.KEY_DICE.equals(key)) {
			if (sharedPreferences.getBoolean(MyPreferenceActivity.KEY_DICE, true)) {
				ibDice6.setVisibility(View.VISIBLE);
			} else {
				ibDice6.setVisibility(View.INVISIBLE);
			}
		}

		if (key == null || MyPreferenceActivity.KEY_SHAKE_DICE.equals(key)) {
			boolean shake = sharedPreferences.getBoolean(MyPreferenceActivity.KEY_SHAKE_DICE, true);
			if (shake)
				registerShakeDice();
			else
				unregisterShakeDice();
		}

		if (key == null || MyPreferenceActivity.KEY_SHAKE_DICE.equals(key)) {
			boolean visible = sharedPreferences.getBoolean(MyPreferenceActivity.KEY_TOOLTIPS, true);
			if (visible && helperLayout == null)
				findViewById(R.id.tt_fight).setVisibility(View.VISIBLE);
			else
				findViewById(R.id.tt_fight).setVisibility(View.GONE);
		}

		super.onSharedPreferenceChanged(sharedPreferences, key);
	}

	private void unregisterShakeDice() {

		if (mShaker != null) {
			mShaker.setOnShakeListener(null);
			mShaker = null;
		}
	}

	private void registerShakeDice() {

		if (mShaker == null) {

			final Vibrator vibe = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);

			mShaker = new ShakeListener(this);

			mShaker.setOnShakeListener(new ShakeListener.OnShakeListener() {
				public void onShake() {
					vibe.vibrate(100);
					ibDice6.rollDice6();
				}
			});
		}
	}

	public boolean applyTheme() {
		boolean ok = true;

		ok &= playerLayout.updateBackground();
		if (helperLayout != null)
			ok &= helperLayout.updateBackground();

		Drawable monsterBg = ThemeManager.getThemeDrawable(ThemeItem.BgMonster);
		monsterLayout.setBackgroundDrawable(monsterBg);

		Drawable bg = ThemeManager.getThemeDrawable(ThemeItem.BgFightLandscape);
		playerVsLayout.setBackgroundDrawable(bg);

		return bg != null && ok && monsterBg != null;
	}

	@Override
	protected void initBluetoothService(BluetoothService service) {
		service.setBluetoothServiceListener(this);
	}

	@Override
	public synchronized void onResume() {
		super.onResume();

		if (mShaker != null) {
			mShaker.resume();
		}

	}

	@Override
	protected void onPause() {
		super.onPause();

		if (mShaker != null)
			mShaker.pause();

	}

	class DeviceChecker extends AsyncTask<String, String, Set<String>> {

		private static final int CONNECT_DELAY = 10000;
		private static final int RESPONSE_DELAY = 10000;

		protected Set<String> mCheckDevices;
		private Set<String> mConnectedDevices;
		private Set<String> mFailedDevices;
		private Set<String> mConfirmedDevices;

		/**
		 * 
		 */
		public DeviceChecker() {
			mCheckDevices = new HashSet<String>();
			mConfirmedDevices = new HashSet<String>();
			mFailedDevices = new HashSet<String>();
			mConnectedDevices = new HashSet<String>();
		}

		public synchronized void addConnectedDevice(String address) {
			mConnectedDevices.add(address);
			notify();
		}

		public synchronized void addConfirmedDevice(String address) {
			mConfirmedDevices.add(address);
			notify();
		}

		public synchronized void addFailedDevice(String address) {
			mFailedDevices.add(address);
			notify();
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see android.os.AsyncTask#doInBackground(Params[])
		 */
		@Override
		protected synchronized Set<String> doInBackground(String... addresses) {
			mCheckDevices.addAll(Arrays.asList(addresses));

			while (!mCheckDevices.isEmpty()) {

				String address = mCheckDevices.iterator().next();
				Debug.verbose("Checking " + address);

				getBluetoothService().connect(address);
				try {
					wait(CONNECT_DELAY);
				} catch (InterruptedException e) {
					Debug.verbose("Wakeup call connect");
				}

				if (mConnectedDevices.contains(address)) {
					getBluetoothService().sendCommand(CMD_REQUEST_ADD_INFO);

					try {
						wait(RESPONSE_DELAY);
					} catch (InterruptedException e) {
						Debug.verbose("Wakeup call message");
					}

					if (mConfirmedDevices.contains(address)) {
						Debug.verbose("Success adding device " + address + " checking next");
						publishProgress("Added player from " + address);
					} else {
						Debug.verbose("No response received from " + address);
						mFailedDevices.add(address);
						publishProgress("Could not connect with " + address);
					}

				} else {
					Debug.verbose("Couldn't connect with " + address);
					mFailedDevices.add(address);
					publishProgress("Could not connect with " + address);
				}

				mCheckDevices.remove(address);

			}

			return mConfirmedDevices;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see android.os.AsyncTask#onProgressUpdate(Progress[])
		 */
		@Override
		protected void onProgressUpdate(String... values) {
			Toast.makeText(FightActivity.this, values[0], Toast.LENGTH_SHORT).show();
			super.onProgressUpdate(values);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see android.os.AsyncTask#onPostExecute(java.lang.Object)
		 */
		@Override
		protected void onPostExecute(Set<String> result) {
			super.onPostExecute(result);
		}
	}

}
